package client

import (
	"context"
	"fmt"
	"io"

	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	pb "wowCoupon.com/rpcserver/business/ber"
)

// BusinessRpcClient provides some basic methods for caller to use.
// These methods wrap grpc client stub methods, you can consider BusinessRpcClient
// as an example, and write your customized BusinessRpcClient if you need.
type BusinessRpcClient struct {
	addr       string
	conn       *grpc.ClientConn
	grpcClient pb.BerClient
}

func NewRpcClient(addr, username, password string) *BusinessRpcClient {
	rc := BusinessRpcClient{}
	rc.addr = addr
	var err error

	// config TLS cert
	//creds, err := credentials.NewClientTLSFromFile("../../../cert/server.crt", "")
	//if err != nil {
	//panic(err)
	//}

	requestToken := &AuthToken{Token: "", MethodType: "Login"}
	rc.conn, err = grpc.Dial(
		addr,
		grpc.WithTransportCredentials(insecure.NewCredentials()),
		//grpc.WithTransportCredentials(creds),
		grpc.WithPerRPCCredentials(requestToken),
	)

	if err != nil {
		panic(err)
	}

	rc.grpcClient = pb.NewBerClient(rc.conn)

	//ctx := context.Background()
	//ctx = context.WithValue(ctx, "methodType", "Login")
	//fmt.Println(ctx.Value("methodType"))
	loginReply, err := rc.grpcClient.Login(
		context.Background(),
		&pb.LoginRequest{Username: username, Password: password},
	)

	if err != nil {
		panic(err)
	}

	// login fail
	if loginReply.Code != 200 {
		panic(fmt.Sprintf("%d : %s", loginReply.Code, loginReply.Msg))
	}

	// login successfully
	// create request token
	requestToken = &AuthToken{Token: loginReply.Token, MethodType: "normal"}

	// get a new rpc connection with request token
	rc.conn.Close()
	rc.conn, err = grpc.Dial(
		addr,
		grpc.WithTransportCredentials(insecure.NewCredentials()),
		//grpc.WithTransportCredentials(creds),
		grpc.WithPerRPCCredentials(requestToken),
	)

	rc.grpcClient = pb.NewBerClient(rc.conn)

	if err != nil {
		panic(err)
	}

	return &rc
}

func (rc *BusinessRpcClient) GetCouponMetas(conditions []string,
	// need provide a callback to handle the rpc reply
	callback func(*pb.GetCouponMetaReply) error) (code int64, msg string) {

	req := &pb.GetCouponMetaRequest{Conditions: conditions}

	stream, err := rc.grpcClient.GetCouponMetas(context.Background(), req)
	if err != nil {
		return 500, fmt.Sprintf("<rpcclient> GetCouponMetas error: %s", err)
	}

	count := 0
	for {
		reply, err := stream.Recv()
		if err == io.EOF {
			fmt.Println(count)
			return 200, "OK"
		}
		if err != nil {
			return 500, fmt.Sprintf("<rpcclient> GetCouponMetas error: %s", err)
		}

		count++

		if err := callback(reply); err != nil {
			return 500, fmt.Sprintf("<rpcclient> <GetCouponMetas> callback returns error: %s", err)
		}
	}
}

func (rc *BusinessRpcClient) AddCouponMeta(businessId int64, goodId int64, metaType int64, title string, amount int64,
	comments string, issueStartTime string, issueEndTime string, validStartTime string, validEndTime string) (code int64, msg string) {

	// 创建发送结构体
	req := pb.CouponMetaRequest{
		BusinessId:     businessId,
		GoodId:         goodId,
		Type:           metaType,
		Title:          title,
		Amount:         amount,
		Comments:       comments,
		IssueStartTime: issueStartTime,
		IssueEndTime:   issueEndTime,
		ValidStartTime: validStartTime,
		ValidEndTime:   validEndTime,
	}

	// 调用我们的服务(AddCouponMeta)
	reply, err := rc.grpcClient.AddCouponMeta(context.Background(), &req)
	fmt.Printf("reply: %s, err: %s", reply, err)
	if err != nil {
		return 500, fmt.Sprintf("<rpcclient> AddCouponMeta error: %s", err)
	}

	//返回状态与信息
	code = reply.GetCode()
	msg = reply.GetMsg()

	return code, msg
}

// AddCouponMetas adds a batch of coupon metas by client-side stream, will receive a reply from server
// after all requests are done. You need prepare a []*pb.CouponMetaRequest arguments as requests when
// you call this method
func (rc *BusinessRpcClient) AddCouponMetas(reqs []*pb.CouponMetaRequest) (code int64, msg string) {

	// 调用我们的服务(AddCouponMeta)
	stream, err := rc.grpcClient.AddCouponMetas(context.Background())
	if err != nil {
		return 500, fmt.Sprintf("<rpcclient> AddCouponMetas error: %s", err)
	}

	for _, req := range reqs {
		// Stream发送请求数据
		if err := stream.Send(req); err != nil {
			return 500, "Error"
		}
	}

	reply, err := stream.CloseAndRecv()
	if err != nil {
		return 500, fmt.Sprintf("<rpcclient> AddCouponMetas error: %s", err)
	}

	//返回状态与信息
	code = reply.GetCode()
	msg = reply.GetMsg()

	return code, msg
}

func (rc *BusinessRpcClient) ExpiredCouponMetas(
	businessID int64,
	// need provide a callback to handle the rpc reply
	callback func(reply *pb.ExpiredCouponMetaReply) error) (code int64, msg string) {

	// 创建请求结构体
	req := &pb.ExpiredCouponMetaRequest{
		BusinessId: businessID,
	}

	// 调用我们的服务(AddCouponMeta)
	stream, err := rc.grpcClient.ExpiredCouponMetas(context.Background(), req)

	if err != nil {
		return 500, fmt.Sprintf("<rpcclient> ExpiredCouponMetas error: %s", err)
	}

	// 持续从stream中获取消息并处理
	for {
		reply, err := stream.Recv()
		if err == io.EOF {
			return 200, "OK"
		}
		if err != nil {
			return 500, err.Error()
		}

		if err := callback(reply); err != nil {
			return 500, fmt.Sprintf("<rpcclient> <ExpiredCouponMetas> callback returns error: %s", err)
		}
	}
}

// AuthToken customer client端自定义认证，client获取server发来的token以后，再进行请求时使用
type AuthToken struct {
	Token      string
	MethodType string
}

func (c AuthToken) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
	return map[string]string{
		"authorization": c.Token,
		"methodtype":    c.MethodType,
	}, nil
}

func (c AuthToken) RequireTransportSecurity() bool {
	return false
}
